/*
 * Copyright 2007-2009 Perttu "celeron55" Ahola <celeron55@gmail.com>
 *
 * This file is part of jukumagic.
 *
 * jukumagic is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * jukumagic is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with jukumagic.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "boardwindow.h"
#include "common.h"
#include "jukuapp.h"
#include "component.h"
#include "cut.h"
#include "narrowcut.h"
#include "arc.h"
#include "line.h"
#include "rect.h"
#include "pin.h"

#include <math.h>
#include <assert.h>
#include <iostream>
#include <algorithm>

IMPLEMENT_DYNAMIC_CLASS(BoardScrolledWindow, wxScrolledWindow)

BEGIN_EVENT_TABLE(BoardScrolledWindow, wxScrolledWindow)

	EVT_PAINT(BoardScrolledWindow::OnPaint)
	EVT_SIZE(BoardScrolledWindow::OnSize)
	
	EVT_LEFT_DOWN(BoardScrolledWindow::OnLeftDown)
	EVT_LEFT_UP(BoardScrolledWindow::OnLeftUp)
	EVT_MIDDLE_DOWN(BoardScrolledWindow::OnMiddleDown)
	EVT_MIDDLE_UP(BoardScrolledWindow::OnMiddleUp)
	EVT_MOTION(BoardScrolledWindow::OnMotion)
	EVT_MOUSEWHEEL(BoardScrolledWindow::OnMouseWheel)

    EVT_CHAR(BoardScrolledWindow::OnChar)
    EVT_KEY_DOWN(BoardScrolledWindow::OnKeyDown)
    EVT_KEY_UP(BoardScrolledWindow::OnKeyUp)

	EVT_ERASE_BACKGROUND(BoardScrolledWindow::OnEraseBackground)

END_EVENT_TABLE()

void BoardScrolledWindow::SetStartValues()
{
	m_board = NULL;
	m_scale = 1.0;
	m_modestatictext = NULL;
	m_last_px_scroll_x = 0;
	m_last_px_scroll_y = 0;
	SetMouseMode(MODE_SELECT);
	m_drag_hysteresis_exceeded = false;
	m_last_mouse_sx = 0;
	m_last_mouse_sy = 0;
	m_last_mouse_hx = 0;
	m_last_mouse_hy = 0;
	m_holerectangle_drawn = false;
	m_zoom_rotation = 0;
	m_middle_is_down = false;
	m_middle_drag_triggered = false;
	m_selection_bitmap = NULL;
	m_selected_jumper_wire_head = -1;
	m_selected_jumper_wire = NULL;
	m_board_padding_log_x = wxGetApp().m_settings.m_board_min_padding_log_x;
	m_board_padding_log_y = wxGetApp().m_settings.m_board_min_padding_log_y;
	m_board_first_hole_log_x = wxGetApp().m_settings.m_board_min_padding_log_x + 50;
	m_board_first_hole_log_y = wxGetApp().m_settings.m_board_min_padding_log_y + 50;
	m_ac_left_down = false;
	m_mch_component = NULL;
	m_keystate_control = false;
	m_keystate_shift = false;
	SetBackgroundColour(wxGetApp().m_settings.m_colour_board_bg);
}

BoardScrolledWindow::BoardScrolledWindow()
		: wxScrolledWindow()
{
	SetStartValues();
}

BoardScrolledWindow::BoardScrolledWindow(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size)
		: wxScrolledWindow(parent, id, pos, size, wxSUNKEN_BORDER, _T("board view"))
{
	SetStartValues();
}

BoardScrolledWindow::~BoardScrolledWindow()
{
	if(m_selection_bitmap) delete m_selection_bitmap;
	m_selection_bitmap = NULL;
}

void BoardScrolledWindow::OnPaint(wxPaintEvent& event)
{
	//std::cout<<"BoardScrolledWindow::OnPaint()"<<std::endl;

	wxPaintDC dc(this);
	
	int px_scroll_x = 0;
	int px_scroll_y = 0;
	CalcUnscrolledPosition(px_scroll_x, px_scroll_y, &px_scroll_x, &px_scroll_y);

	int px_size_x = 0;
	int px_size_y = 0;
	GetClientSize(&px_size_x, &px_size_y);
	
	int log_scroll_x = (int)((double)px_scroll_x / m_scale);
	int log_scroll_y = (int)((double)px_scroll_y / m_scale);

	/*wxBitmap buffer(px_size_x, px_size_y);
	wxMemoryDC bdc;
	bdc.SelectObject(buffer);
	
	bdc.SetUserScale(m_scale, m_scale);
	bdc.SetPen(*wxBLACK_PEN);*/

	/*int log_scroll_x = 0;
	int log_scroll_y = 0;*/

	{
	/*wxPen pen(wxColour(0,0,0), 0);
	wxBrush brush(wxColour(0,0,0), wxTRANSPARENT);*/
	wxPen pen(wxGetApp().m_settings.m_colour_hole_selection, 0);
	wxBrush brush(wxColour(0,0,0), wxTRANSPARENT);
	dc.SetPen(pen);
	dc.SetBrush(brush);

	int hrect_sx1 = m_scale * (m_last_mouse_hx * 100 + m_board_padding_log_x + 50 - 50);
	int hrect_sy1 = m_scale * (m_last_mouse_hy * 100 + m_board_padding_log_y + 50 - 50);
	int hrect_x = hrect_sx1 - px_scroll_x;
	int hrect_y = hrect_sy1 - px_scroll_y;
	int hrect_sw = m_scale * 100;
	int hrect_sh = m_scale * 100;
	if(IsExposed(hrect_x, hrect_y, hrect_sw, hrect_sh)){
		//dc.DrawRectangle(hrect_sx1, hrect_sy1, hrect_sw, hrect_sh);
		m_holerectangle_drawn = false;
	}
	}

	dc.SetUserScale(m_scale, m_scale);
	wxPen pen1(wxGetApp().m_settings.m_colour_footprint_graphics, 0);
	dc.SetPen(pen1);
	//dc.SetPen(*wxBLACK_PEN);

	if(m_board){
		//std::cout<<"m_board!=NULL"<<std::endl;
		m_board->Draw(dc, this, wxPoint(-log_scroll_x + m_board_padding_log_x, -log_scroll_y + m_board_padding_log_y));
	}

	dc.SetUserScale(1.0, 1.0);

	if(m_mousemode == MODE_MOVE_SELECTION && m_selection_bitmap){
		dc.DrawBitmap(*m_selection_bitmap, m_so_new_sx1 - px_scroll_x,
				m_so_new_sy1 - px_scroll_y, true);
	}
	/*if(m_mousemode == MODE_MOVE_JUMPER_WIRE_HEAD && m_selected_jumper_wire){
	}*/

	//dc.DrawBitmap(buffer, 0, 0, false);
}

void BoardScrolledWindow::OnEraseBackground(wxEraseEvent &event)
{
	event.Skip();
}

void BoardScrolledWindow::ScrollWindow(int dx, int dy, const wxRect *rect)
{
	//std::cout<<"BoardCanvas::ScrollWindow(): (rect==NULL)="<<(rect==NULL)<<std::endl;
	wxWindow::ScrollWindow(dx, dy, rect);
	
	// Make the new area exposed

	int px_scroll_x = 0;
	int px_scroll_y = 0;
	CalcUnscrolledPosition(px_scroll_x, px_scroll_y, &px_scroll_x, &px_scroll_y);

	int px_size_x = 0;
	int px_size_y = 0;
	GetClientSize(&px_size_x, &px_size_y);
	
	//RefreshRect(0, 0, px_size_x, px_size_y);

	if(px_scroll_x > m_last_px_scroll_x){
		int amount = px_scroll_x - m_last_px_scroll_x;
		int x = px_size_x - amount;
		int w = amount;
		int y = 0;
		int h = px_size_y;
		RefreshRect(wxRect(x, y, w, h));
	}
	
	if(px_scroll_x < m_last_px_scroll_x){
		int amount = m_last_px_scroll_x - px_scroll_x;
		int x = 0;
		int w = amount;
		int y = 0;
		int h = px_size_y;
		RefreshRect(wxRect(x, y, w, h));
	}

	if(px_scroll_y > m_last_px_scroll_y){
		int amount = px_scroll_y - m_last_px_scroll_y;
		int x = 0;
		int w = px_size_x;
		int y = px_size_y - amount;
		int h = amount;
		RefreshRect(wxRect(x, y, w, h));
	}

	if(px_scroll_y < m_last_px_scroll_y){
		int amount = m_last_px_scroll_y - px_scroll_y;
		int x = 0;
		int w = px_size_x;
		int y = 0;
		int h = amount;
		RefreshRect(wxRect(x, y, w, h));
	}

	m_last_px_scroll_x = px_scroll_x;
	m_last_px_scroll_y = px_scroll_y;
}

void BoardScrolledWindow::OnSize(wxSizeEvent& event)
{
	wxScrolledWindow::OnSize(event);
	CalcBoardOffsets();
	UpdateVirtualSize();
}

void BoardScrolledWindow::CalcNeededSize(int &w, int &h, double scale)
{
	if(m_board){
		w = (int)(((double)m_board->GetW() * 100 + m_board_padding_log_x*2) * scale);
		h = (int)(((double)m_board->GetH() * 100 + m_board_padding_log_y*2) * scale);
	}
	else{
		w = 0;
		h = 0;
	}
}
void BoardScrolledWindow::UpdateVirtualSize()
{
	return;
	int w, h;
	CalcNeededSize(w, h, m_scale);
	SetVirtualSize(w, h);
}

void BoardScrolledWindow::CalcBoardOffsets()
{
	return;
	if(!m_board){
		m_board_padding_log_x = wxGetApp().m_settings.m_board_min_padding_log_x;
		m_board_padding_log_y = wxGetApp().m_settings.m_board_min_padding_log_y;
	}
	else{
		int b_px_w = m_scale * m_board->GetW() * 100;
		int b_px_h = m_scale * m_board->GetH() * 100;
		//std::cout<<"b_px_w="<<b_px_w<<", b_px_h="<<b_px_h<<std::endl;
		int view_px_w = 0;
		int view_px_h = 0;
		GetClientSize(&view_px_w, &view_px_h);
		//std::cout<<"view_px_w="<<view_px_w<<", view_px_h="<<view_px_h<<std::endl;
		int padding_px_x = (view_px_w - b_px_w)/2;
		if(padding_px_x < m_scale * wxGetApp().m_settings.m_board_min_padding_log_x)
			padding_px_x = m_scale*wxGetApp().m_settings.m_board_min_padding_log_x;
		int padding_px_y = (view_px_h - b_px_h)/2;
		if(padding_px_y < m_scale * wxGetApp().m_settings.m_board_min_padding_log_y)
			padding_px_y = m_scale*wxGetApp().m_settings.m_board_min_padding_log_y;
		m_board_padding_log_x = 1.0/m_scale * padding_px_x;
		m_board_padding_log_y = 1.0/m_scale * padding_px_y;
	}
	m_board_first_hole_log_x = m_board_padding_log_x + 50;
	m_board_first_hole_log_y = m_board_padding_log_y + 50;
	Refresh();
}

#define SELECTRECT_LINE_WIDTH 2

void BoardScrolledWindow::DrawSelectRect(int sx, int sy)
{
	wxClientDC dc(this);
	DoPrepareDC(dc);
	
	dc.SetLogicalFunction(wxINVERT);

	wxPen pen(wxColour(100,0,0), SELECTRECT_LINE_WIDTH);
	wxBrush brush(wxColour(0,0,0), wxTRANSPARENT);
	dc.SetPen(pen);
	dc.SetBrush(brush);
	
	int x1, y1, x2, y2;
	if(sx > m_left_down_sx){
		x1 = m_left_down_sx;
		x2 = sx;
	}
	else{
		x1 = sx;
		x2 = m_left_down_sx;
	}
	if(sy > m_left_down_sy){
		y1 = m_left_down_sy;
		y2 = sy;
	}
	else{
		y1 = sy;
		y2 = m_left_down_sy;
	}

	//draw lines if rectangle is very flat
	if(abs(x2-x1) <= 1){
		dc.DrawLine(x1, y1, x1, y2);
	}
	else if(abs(y2-y1) <= 1){
		dc.DrawLine(x1, y1, x2, y1);
	}
	//else draw the rectangle
	else{
		dc.DrawRectangle(x1, y1, x2-x1, y2-y1);
	}
}

#define TEMPLINE_WIDTH_PX 5

void BoardScrolledWindow::DrawTempLine(int sx1, int sy1, int sx2, int sy2)
{
	wxClientDC dc(this);
	DoPrepareDC(dc);
	
	dc.SetLogicalFunction(wxINVERT);

	wxPen pen(wxColour(100,0,0), TEMPLINE_WIDTH_PX);
	wxBrush brush(wxColour(0,0,0), wxTRANSPARENT);
	dc.SetPen(pen);
	dc.SetBrush(brush);
	
	dc.DrawLine(sx1, sy1, sx2, sy2);
}

void BoardScrolledWindow::CalcSelectionThings()
{
	//std::cerr<<"BoardScrolledWindow::CalcSelectionThings(): m_board = "<<m_board<<std::endl;
	if(m_board == NULL) return;
	if(m_board->GetSelectedObjects().empty()) return;

	m_so_sx1 = BIG_INT_VALUE;
	m_so_sy1 = BIG_INT_VALUE;
	m_so_sx2 = SMALL_INT_VALUE;
	m_so_sy2 = SMALL_INT_VALUE;
	for(std::set<JukuObject*>::const_iterator i = m_board->GetSelectedObjects().begin(); i != m_board->GetSelectedObjects().end(); i++){
		assert((*i)->GetSelected());
		BoardRect br = (*i)->GetDrawArea();
		wxRect px_sr(
				m_scale*(br.m_x + m_board_first_hole_log_x),
				m_scale*(br.m_y + m_board_first_hole_log_y),
				m_scale*(br.m_w),
				m_scale*(br.m_h));
		if(px_sr.x < m_so_sx1) m_so_sx1 = px_sr.x;
		if(px_sr.y < m_so_sy1) m_so_sy1 = px_sr.y;
		if(px_sr.x + px_sr.width > m_so_sx2) m_so_sx2 = px_sr.x + px_sr.width;
		if(px_sr.y + px_sr.height > m_so_sy2) m_so_sy2 = px_sr.y + px_sr.height;
	}
	m_so_sw = m_so_sx2 - m_so_sx1;
	m_so_sh = m_so_sy2 - m_so_sy1;

	if(m_selection_bitmap){
		delete m_selection_bitmap;
		m_selection_bitmap = NULL;
	}

	m_selection_bitmap = new wxBitmap(m_so_sw, m_so_sh, -1);
	wxMemoryDC dc;
	dc.SelectObject(*m_selection_bitmap);
	dc.SetUserScale(m_scale, m_scale);

	wxBitmap maskbitmap(m_so_sw, m_so_sh, 1);
	wxMemoryDC dc2;
	dc2.SelectObject(maskbitmap);

	wxPen pen(wxColour(0,0,0), 0);
	wxBrush brush(wxColour(0,0,0));
	dc2.SetPen(pen);
	dc2.SetBrush(brush);
	dc2.DrawRectangle(0, 0, m_so_sw, m_so_sh);

	dc2.SetUserScale(m_scale, m_scale);
	dc2.SetLogicalFunction(wxINVERT);

	m_so_board_x1 = 1.0/m_scale*(m_so_sx1) - m_board_first_hole_log_x;
	m_so_board_y1 = 1.0/m_scale*(m_so_sy1) - m_board_first_hole_log_y;
	wxPoint log_origin(-m_so_board_x1, -m_so_board_y1);

	for(std::set<JukuObject*>::const_iterator i = m_board->GetSelectedObjects().begin(); i != m_board->GetSelectedObjects().end(); i++){
		(*i)->Draw(dc, NULL, log_origin);
		(*i)->Draw(dc2, NULL, log_origin);
	}
	wxMask *mask = new wxMask(maskbitmap);
	m_selection_bitmap->SetMask(mask);
}

void BoardScrolledWindow::UpdateMoveSelection(int board_x, int board_y)
{
	int mouse_board_dx = board_x - m_left_down_board_x;
	int mouse_board_dy = board_y - m_left_down_board_y;

	int mouse_board_holes_x;
	int mouse_board_holes_y;
	if(mouse_board_dx>=0) mouse_board_holes_x = (mouse_board_dx+50)/100;
	else                  mouse_board_holes_x = (mouse_board_dx-50)/100;
	if(mouse_board_dy>=0) mouse_board_holes_y = (mouse_board_dy+50)/100;
	else                  mouse_board_holes_y = (mouse_board_dy-50)/100;

	m_so_move_board_dx = mouse_board_holes_x*100;
	m_so_move_board_dy = mouse_board_holes_y*100;
	
	m_so_new_board_x1 = m_so_board_x1 + m_so_move_board_dx;
	m_so_new_board_y1 = m_so_board_y1 + m_so_move_board_dy;
	
	m_so_new_logx1 = m_so_new_board_x1 + m_board_first_hole_log_x;
	m_so_new_logy1 = m_so_new_board_y1 + m_board_first_hole_log_y;

	RefreshRect(wxRect(m_so_new_sx1 - m_last_px_scroll_x, m_so_new_sy1 - m_last_px_scroll_y,
			m_so_sw, m_so_sh));
	
	m_so_new_sx1 = m_scale * m_so_new_logx1;
	m_so_new_sy1 = m_scale * m_so_new_logy1;
}

void BoardScrolledWindow::UpdateMoveJumperWireHead(int board_x, int board_y)
{
	if(!m_selected_jumper_wire) return;
	
	int mouse_board_dx = board_x - m_left_down_board_x;
	int mouse_board_dy = board_y - m_left_down_board_y;

	int mouse_board_holes_x;
	int mouse_board_holes_y;
	if(mouse_board_dx>=0) mouse_board_holes_x = (mouse_board_dx+50)/100;
	else                  mouse_board_holes_x = (mouse_board_dx-50)/100;
	if(mouse_board_dy>=0) mouse_board_holes_y = (mouse_board_dy+50)/100;
	else                  mouse_board_holes_y = (mouse_board_dy-50)/100;

	int m_sjwh_move_board_dx = mouse_board_holes_x*100;
	int m_sjwh_move_board_dy = mouse_board_holes_y*100;
	
	int m_sjwh_new_board_x = m_sjwh_orig_point.m_x + m_sjwh_move_board_dx;
	int m_sjwh_new_board_y = m_sjwh_orig_point.m_y + m_sjwh_move_board_dy;
	
	m_selected_jumper_wire->SetHeadPoint(m_selected_jumper_wire_head,
			BoardPoint(m_sjwh_new_board_x, m_sjwh_new_board_y));
	
	/*m_board->ChangedSomething();
	CalcSelectionThings();
	Refresh();*/
}

bool BoardScrolledWindow::Rotate180Selection()
{
	//std::cout<<"BoardScrolledWindow::Rotate180Selection"<<std::endl;
	if(m_board->GetSelectedObjects().empty()){
		//std::cout<<"m_board->GetSelectedObjects().empty()"<<std::endl;
		return false;
	}
	int x1=BIG_INT_VALUE, y1=BIG_INT_VALUE, x2=SMALL_INT_VALUE, y2=SMALL_INT_VALUE;
	for(std::set<JukuObject*>::const_iterator i = m_board->GetSelectedObjects().begin();
			i != m_board->GetSelectedObjects().end(); i++){
		BoardRect r = (*i)->GetPinExtents();
		if(r.m_x < x1) x1 = r.m_x;
		if(r.m_y < y1) y1 = r.m_y;
		if(r.m_x + (int)r.m_w > x2) x2 = r.m_x + r.m_w;
		if(r.m_y + (int)r.m_h > y2) y2 = r.m_y + r.m_h;
	}
	//std::cout<<x1<<", "<<y1<<", "<<x2<<", "<<y2<<std::endl;
	BoardPoint origin((x1+x2)/2, (y1+y2)/2);
	//std::cout<<origin.m_x<<", "<<origin.m_y<<std::endl;
	for(std::set<JukuObject*>::const_iterator i = m_board->GetSelectedObjects().begin();
			i != m_board->GetSelectedObjects().end(); i++){
		(*i)->Rotate(CCW, origin);
		(*i)->Rotate(CCW, origin);
	}
	/*m_board->ChangedSomething();
	CalcSelectionThings();
	Refresh();*/
	return true;
}

bool BoardScrolledWindow::Rotate90Selection()
{
	//std::cout<<"BoardScrolledWindow::Rotate90Selection"<<std::endl;
	if(m_board->GetSelectedObjects().empty()){
		//assert(0);
		//std::cout<<"m_board->GetSelectedObjects().empty()"<<std::endl;
		return false;
	}
	int x1=BIG_INT_VALUE, y1=BIG_INT_VALUE, x2=SMALL_INT_VALUE, y2=SMALL_INT_VALUE;
	for(std::set<JukuObject*>::const_iterator i = m_board->GetSelectedObjects().begin();
			i != m_board->GetSelectedObjects().end(); i++){
		BoardRect r = (*i)->GetPinExtents();
		if(r.m_x < x1) x1 = r.m_x;
		if(r.m_y < y1) y1 = r.m_y;
		if(r.m_x + (int)r.m_w > x2) x2 = r.m_x + r.m_w;
		if(r.m_y + (int)r.m_h > y2) y2 = r.m_y + r.m_h;
	}
	//std::cout<<x1<<", "<<y1<<", "<<x2<<", "<<y2<<std::endl;
	int center_x = (x1+x2)/2;
	int center_y = (y1+y2)/2;
	//make sure the pins will snap to holes when rotated
	if(!((center_x % 100 == 0 && center_y % 100 == 0)
			|| ((center_x+50) % 100 == 0 && (center_y+50) % 100 == 0))){
		//this conditioning makes sure everything will be at the same place after four rotations
		if(center_y % 100 == 0) center_x += 50;
		else center_x -= 50;
	}
	BoardPoint origin(center_x, center_y);
	//std::cout<<origin.m_x<<", "<<origin.m_y<<std::endl;
	for(std::set<JukuObject*>::const_iterator i = m_board->GetSelectedObjects().begin();
			i != m_board->GetSelectedObjects().end(); i++){
		(*i)->Rotate(CCW, origin);
	}
	return true;
}

void BoardScrolledWindow::ContentsChanged()
{
	// refresh
	CalcSelectionThings();
	Refresh();
}

void BoardScrolledWindow::SelectionChanged()
{
	// refresh
	CalcSelectionThings();
	Refresh();
}

void GetMouseModeText(BoardWindowMouseMode mode, wxString &text)
{
	switch(mode){
	case MODE_SELECT:
		text = wxT("Select");
		break;
	case MODE_LEFT_SELECT:
		text = wxT("Select Active");
		break;
	case MODE_LEFT_SELECT_RECTANGLE:
		text = wxT("Select Rectangle");
		break;
	case MODE_DOWN_ON_SELECTION:
		text = wxT("Click on Selection");
		break;
	case MODE_MOVE_SELECTION:
		text = wxT("Move Selection");
		break;
	case MODE_MOVE_JUMPER_WIRE_HEAD:
		text = wxT("Move Jumper Wire Head");
		break;
	case MODE_MOVE_COMPONENT_HEAD:
		text = wxT("Move Component Head");
		break;
	case MODE_ADD_JUMPER_WIRE:
		text = wxT("Add Jumper Wire");
		break;
	case MODE_ADD_JUMPER_WIRE_BOTTOM:
		text = wxT("Add Jumper Wire (bottom)");
		break;
	case MODE_ADD_CUT:
		text = wxT("Add Cut");
		break;
	case MODE_ADD_NARROWCUT:
		text = wxT("Add Narrow Cut");
		break;
	case MODE_ADD_RESISTOR:
		text = wxT("Add Resistor");
		break;
	case MODE_ADD_DIODE:
		text = wxT("Add Diode");
		break;
	case MODE_ADD_CERAMIC:
		text = wxT("Add Small Capacitor");
		break;
	case MODE_DELETE:
		text = wxT("Delete");
		break;
	default:
		text = wxT("Unknown Mode");
	}
}

void BoardScrolledWindow::UpdateModeStaticText()
{
	if(m_modestatictext == NULL) return;
	
	wxString text;
	GetMouseModeText(m_mousemode, text);
	m_modestatictext->SetLabel(text);
}

int GetMouseModeTextMaxLen(wxWindow *w)
{
	int len = 0;
	for(int i=0; i<MODE_COUNT; i++){
		wxString text;
		GetMouseModeText((BoardWindowMouseMode)i, text);
		int x, y;
		w->GetTextExtent(text, &x, &y);
		if(x > len) len = x;
	}
	return len;
}

void BoardScrolledWindow::SetMouseMode(BoardWindowMouseMode newmode)
{
	m_mousemode = newmode;
	
	UpdateModeStaticText();
}

void BoardScrolledWindow::OnLeftDown(wxMouseEvent& event)
{
	//std::cout<<"BoardScrolledWindow::OnLeftDown"<<std::endl;
	event.Skip(); //allow mouse event to select window

	if(!m_board) return;
	
	int wx = event.GetX();
	int wy = event.GetY();
	int sx = wx + m_last_px_scroll_x;
	int sy = wy + m_last_px_scroll_y;
	int board_x = sx / m_scale - m_board_padding_log_x;
	int board_y = sy / m_scale - m_board_padding_log_y;
	/*std::cout<<"at ("<<wx<<","<<wy<<")"
			" ("<<sx<<","<<sy<<")"
			" ("<<board_x<<","<<board_y<<")"
			<<std::endl;*/

	m_left_down_board_x = board_x;
	m_left_down_board_y = board_y;
	
	m_left_down_sx = sx;
	m_left_down_sy = sy;

	m_drag_hysteresis_exceeded = false;
	switch(m_mousemode){
	case MODE_SELECT:
		{
			bool something_done = false;
			
			//if some objects are selected
			if(!m_board->GetSelectedObjects().empty()){
				//get the objects that would be selected by clicking at this point
				std::set<JukuObject*> objects = m_board->GetSelectedObjects(
						board_x, board_y, board_x, board_y, true);

				//if went down on some selected objects or if pressing shift
				bool start_down_on_selection = GetShiftState();
				if(!start_down_on_selection){
					for(std::set<JukuObject*>::iterator i = objects.begin(); i!=objects.end(); i++){
						if((*i)->GetSelected()){
							start_down_on_selection = true;
							break;
						}
					}
				}
				if(start_down_on_selection){
					SetMouseMode(MODE_DOWN_ON_SELECTION);
					something_done = true;
				}
			}
			//if did nothing, then start select mode
			if(!something_done){
				//std::cout<<"going to select mode"<<std::endl;
				SetMouseMode(MODE_LEFT_SELECT);
			}
		}
		break;
	case MODE_LEFT_SELECT:
		break;
	case MODE_LEFT_SELECT_RECTANGLE:
		break;
	case MODE_DOWN_ON_SELECTION:
		break;
	case MODE_MOVE_SELECTION:
		break;
	case MODE_MOVE_JUMPER_WIRE_HEAD:
		break;
	case MODE_MOVE_COMPONENT_HEAD:
		{
			m_mch_component = NULL;
			//get the objects that would be selected by clicking at this point
			std::set<JukuObject*> objects = m_board->GetSelectedObjects(
					board_x, board_y, board_x, board_y, true);
			//search for a 2-wire-legged component with a head matching
			//to the coordinates of the click and which is selected already
			for(std::set<JukuObject*>::iterator i = objects.begin(); i != objects.end(); i++){
				JukuObject *object = (*i);
				//if(object->GetSelected() == false) continue;
				if(object->GetType() != TYPE_COMPONENT) continue;
				Component *c = (Component*)object;
				Footprint *f = c->GetFootprint();
				//only these have movable heads
				if(f->m_name.substr(0,3)!=wxT("ACY")
						&& f->m_name.substr(0,3)!=wxT("ALF")
						&& f->m_name.substr(0,3)!=wxT("ACC")) continue;
				bool found = false;
				for(std::list<JukuObject*>::const_iterator i = f->m_pins.GetObjects().begin();
						i != f->m_pins.GetObjects().end(); i++){
					JukuObject *object = (*i);
					if(object->GetType() != TYPE_PIN) continue;
					Pin *pin = (Pin*)object;
					int x = c->GetBoardPos().m_x + pin->m_x;
					int y = c->GetBoardPos().m_y + pin->m_y;
					if(abs(m_left_down_board_x-50 - x) >= 50
							|| abs(m_left_down_board_y-50 - y) >= 50) continue;
					m_mch_component = c;
					m_mch_moved_head = pin->m_num;
					
					m_mch_other_pin_x = SMALL_INT_VALUE;
					m_mch_other_pin_y = SMALL_INT_VALUE;
					for(std::list<JukuObject*>::const_iterator i = f->m_pins.GetObjects().begin();
							i != f->m_pins.GetObjects().end(); i++){
						if((*i)->GetType() != TYPE_PIN) continue;
						Pin *p1 = (Pin*)(*i);
						if(p1->m_num != m_mch_moved_head){
							m_mch_other_pin_x = p1->m_x + c->GetBoardPos().m_x;
							m_mch_other_pin_y = p1->m_y + c->GetBoardPos().m_y;
							break;
						}
					}
					if(m_mch_other_pin_x == SMALL_INT_VALUE) continue;

					m_mch_templine_sx1 = m_scale * (m_mch_other_pin_x + m_board_first_hole_log_x);
					m_mch_templine_sy1 = m_scale * (m_mch_other_pin_y + m_board_first_hole_log_y);

					int board_x2_holes = (board_x-50+50)/100;
					int board_y2_holes = (board_y-50+50)/100;
					m_mch_move_pin_x = board_x2_holes * 100;
					m_mch_move_pin_y = board_y2_holes * 100;
					m_mch_templine_sx2 = m_scale * (m_mch_move_pin_x + m_board_first_hole_log_x);
					m_mch_templine_sy2 = m_scale * (m_mch_move_pin_y + m_board_first_hole_log_y);

					DrawTempLine(m_mch_templine_sx1, m_mch_templine_sy1,
							m_mch_templine_sx2, m_mch_templine_sy2);
					found = true;
					break;
				}
				if(found){
					break;
				}
			}
		}
		break;
#if 0
	case MODE_ADD_JUMPER_WIRE:
	case MODE_ADD_JUMPER_WIRE_BOTTOM:
		{
			int board_x_holes = (board_x-50+50)/100;
			int board_y_holes = (board_y-50+50)/100;
			int board_x_centered = board_x_holes * 100;
			int board_y_centered = board_y_holes * 100;
			JumperWire *wire = new JumperWire();
			wire->SetHeadPoint(1, BoardPoint(board_x_centered, board_y_centered));
			wire->SetHeadPoint(2, BoardPoint(board_x_centered, board_y_centered));
			if(m_mousemode==MODE_ADD_JUMPER_WIRE_BOTTOM) wire->SetSide(BOTTOM);
			if(m_board) m_board->SetSelected(false);
			//ObjectSetSelected(wire, true);
			wire->SetSelected(true);
			m_board->Add((JukuObject*)wire, NULL);

			/*m_board->GetSelectedObjects().clear();
			m_board->GetSelectedObjects().push_back((JukuObject*)wire);
			SetMouseMode(MODE_DOWN_ON_SELECTION);*/

			m_selected_jumper_wire_head = 2;
			m_selected_jumper_wire = wire;
			m_sjwh_orig_point = wire->GetHeadPoint(2);
			if(m_mousemode == MODE_ADD_JUMPER_WIRE) SetMouseMode(MODE_MOVE_JUMPER_WIRE_HEAD_AFTER_ADD);
			else SetMouseMode(MODE_MOVE_JUMPER_WIRE_HEAD_AFTER_ADD_BOTTOM);
			UpdateMoveJumperWireHead(board_x, board_y);
			
			//Refresh();
		}
		break;
#endif
	case MODE_ADD_CUT:
		break;
	case MODE_ADD_NARROWCUT:
		break;
	case MODE_ADD_JUMPER_WIRE:
	case MODE_ADD_JUMPER_WIRE_BOTTOM:
	case MODE_ADD_RESISTOR:
	case MODE_ADD_DIODE:
	case MODE_ADD_CERAMIC:
		{
			int board_x1_holes = (m_left_down_board_x-50+50)/100;
			int board_y1_holes = (m_left_down_board_y-50+50)/100;
			m_ac_board_x1_centered = board_x1_holes * 100;
			m_ac_board_y1_centered = board_y1_holes * 100;
			int board_x2_holes = (board_x-50+50)/100;
			int board_y2_holes = (board_y-50+50)/100;
			m_ac_board_x2_centered = board_x2_holes * 100;
			m_ac_board_y2_centered = board_y2_holes * 100;
			m_ac_templine_sx1 = m_scale * (m_ac_board_x1_centered + m_board_first_hole_log_x);
			m_ac_templine_sy1 = m_scale * (m_ac_board_y1_centered + m_board_first_hole_log_y);
			m_ac_templine_sx2 = m_scale * (m_ac_board_x2_centered + m_board_first_hole_log_x);
			m_ac_templine_sy2 = m_scale * (m_ac_board_y2_centered + m_board_first_hole_log_y);
			DrawTempLine(m_ac_templine_sx1, m_ac_templine_sy1,
					m_ac_templine_sx2, m_ac_templine_sy2);
			m_ac_left_down = true;
		}
		break;
	case MODE_DELETE:
		break;
	default:
		std::cerr<<"WARNING: Invalid mousemode\n"<<std::endl;
	}
}

void BoardScrolledWindow::OnLeftUp(wxMouseEvent& event)
{
	//std::cout<<"BoardScrolledWindow::OnLeftUp"<<std::endl;

	if(!m_board) return;
	
	int wx = event.GetX();
	int wy = event.GetY();
	int sx = wx + m_last_px_scroll_x;
	int sy = wy + m_last_px_scroll_y;
	int board_x = sx / m_scale - m_board_padding_log_x;
	int board_y = sy / m_scale - m_board_padding_log_y;
	/*std::cout<<"at ("<<wx<<","<<wy<<")"
			" ("<<sx<<","<<sy<<")"
			" ("<<board_x<<","<<board_y<<")"
			<<std::endl;*/
	switch(m_mousemode){
	case MODE_SELECT:
		break;
	case MODE_LEFT_SELECT:
		//clicked something
		{
			/*std::cout<<"clicked: board ("<<m_left_down_board_x<<","
					<<m_left_down_board_y<<")"<<std::endl;*/
					
			//std::cout<<"GetControlState()="<<GetControlState()<<std::endl;

			if(GetControlState() == false){
				m_board->SetSelected(false);
				//m_board->GetSelectedObjects().clear();
			}
			
			JukuObject *object = m_board->GetSelectedObject(
					m_left_down_board_x, m_left_down_board_y);
			if(object){
				//std::cout<<"selected "<<wxString(object->GetShortString()).mb_str(wxConvUTF8)<<std::endl;
				//we shouldn't be here if the object was selected already,
				//so continue without further checks
				object->SetSelected(true);
				//m_board->GetSelectedObjects().push_back(object);
			}
			else{
				//std::cout<<"selected nothing"<<std::endl;
			}
			
			/*CalcSelectionThings();
			Refresh();*/

			SetMouseMode(MODE_SELECT);
		}
		break;
	case MODE_LEFT_SELECT_RECTANGLE:
		{
			//selected an area

			//remove selection rectangle
			DrawSelectRect(m_last_mouse_sx, m_last_mouse_sy);

			int px_x1 = m_left_down_sx;
			int px_y1 = m_left_down_sy;
			int px_x2 = m_last_mouse_sx;
			int px_y2 = m_last_mouse_sy;

			int log_x1 = px_x1 / m_scale;
			int log_y1 = px_y1 / m_scale;
			int log_x2 = px_x2 / m_scale;
			int log_y2 = px_y2 / m_scale;

			int board_x1u = log_x1 - m_board_padding_log_x;
			int board_y1u = log_y1 - m_board_padding_log_y;
			int board_x2u = log_x2 - m_board_padding_log_x;
			int board_y2u = log_y2 - m_board_padding_log_y;
			
			int board_x1, board_x2;
			if(board_x1u < board_x2u){ board_x1 = board_x1u; board_x2 = board_x2u; }
			else                     { board_x1 = board_x2u; board_x2 = board_x1u; }
			int board_y1, board_y2;
			if(board_y1u < board_y2u){ board_y1 = board_y1u; board_y2 = board_y2u; }
			else                     { board_y1 = board_y2u; board_y2 = board_y1u; }

			/*std::cout<<"selected area: ("<<px_x1<<", "<<px_y1
					<<", "<<px_x2<<", "<<px_y2<<")"
					<<" -> logical: ("<<log_x1<<", "<<log_y1
					<<", "<<log_x2<<", "<<log_y2<<")"
					<<" -> board: ("<<board_x1<<", "<<board_y1
					<<", "<<board_x2<<", "<<board_y2<<")"
					<<std::endl;*/
			
			if(GetControlState() == false){
				m_board->SetSelected(false);
				//m_board->GetSelectedObjects().clear();
			}
				
			std::set<JukuObject*> newly_selected_objects
					= m_board->GetSelectedObjects(board_x1, board_y1,
							board_x2, board_y2, wxGetApp().m_settings.m_select_touch_is_enough);

			if(newly_selected_objects.empty()){
				//std::cout<<"selected no objects"<<std::endl;
			}
			else{
				/*std::cout<<"selected ";
				JukuObject::PrintList(newly_selected_objects);
				std::cout<<std::endl;*/

				m_board->SelectChildren(newly_selected_objects);
			}
			
			//Refresh();

			SetMouseMode(MODE_SELECT);
		}
		break;
	case MODE_DOWN_ON_SELECTION: //went down, now comes up
		{
			//std::cout<<"went down, now comes up"<<std::endl;
			//get the objects that would be selected by clicking at this point
			std::set<JukuObject*> objects = m_board->GetSelectedObjects(
					m_left_down_board_x, m_left_down_board_y,
					m_left_down_board_x, m_left_down_board_y, true);

			/*std::cout<<"objects that would be selected: ";
			JukuObject::PrintList(objects);
			std::cout<<std::endl;*/

			bool something_done = false;
			
			std::set<JukuObject*> selected_selected_objects;
			for(std::set<JukuObject*>::iterator i = objects.begin();
					i != objects.end(); i++){
				if((*i)->GetSelected() == true){
					selected_selected_objects.insert(*i);
				}
			}
			if(selected_selected_objects.size() == 1){
				//std::cout<<"one selected object"<<std::endl;
				//if we have control pressed down, the object should be unselected
				if(GetControlState() == true){
					//std::cout<<"control is down"<<std::endl;
					JukuObject *object = *selected_selected_objects.begin();
					//do it
					object->SetSelected(false);
					/*for(std::set<JukuObject*>::const_iterator i = m_board->GetSelectedObjects().begin(); i != m_board->GetSelectedObjects().end(); i++){
						if((*i) == object){
							m_board->GetSelectedObjects().erase(i);
							break;
						}
					}*/
					/*CalcSelectionThings();
					Refresh();*/
					something_done = true;
				}
			}
			
			if(!something_done){
				//attempt to cycle selection if only one object is selected
				if(m_board->GetSelectedObjects().size()==1){
					std::cout<<"attempting to cycle selection"<<std::endl;
				
					if(objects.size() == 0){
						//this happens when an object is selected and the mouse is clicked
						//but not dragged in some other location than the component
					}
					else if(objects.size() == 1){
						//only one object at this point, so cannot cycle
						//std::cout<<"only one object at this point, cannot cycle"<<std::endl;
					}
					else{
						/*int a = 0;
						for(std::set<JukuObject*>::iterator i = objects.begin();
								i != objects.end(); i++){
							std::cout<<"object number "<<a<<" is "<<(unsigned int)(*i)<<std::endl;
							a++;
						}*/

						JukuObject *old_object = *m_board->GetSelectedObjects().begin();
						//std::cout<<"old object is "<<(unsigned int)old_object<<std::endl;
						JukuObject *new_object = NULL;
						//a=0;
						for(std::set<JukuObject*>::iterator i = objects.begin();
								i != objects.end(); i++){
							if((*i) == old_object){
								//std::cout<<"old object found at "<<a<<std::endl;
								i++;
								if(i == objects.end()){
									//std::cout<<"taking new object from 0"<<std::endl;
									new_object = *objects.begin();
									break;
								}
								//std::cout<<"taking new object from "<<(a+1)<<std::endl;
								new_object = (*i);
							}
							//a++;
						}
						if(new_object != NULL){
							assert(new_object != NULL);
							//m_board->GetSelectedObjects().clear();
							old_object->SetSelected(false);
							new_object->SetSelected(true);
							//m_board->GetSelectedObjects().push_back(new_object);
							/*CalcSelectionThings();
							Refresh();*/
						}
						something_done = true;
					}
				}
			}
			SetMouseMode(MODE_SELECT);
		}
		break;
	case MODE_MOVE_SELECTION:
		{
			m_board->MoveChildren(m_board->GetSelectedObjects(), m_so_move_board_dx, m_so_move_board_dy);
			/*for(std::set<JukuObject*>::const_iterator i = m_board->GetSelectedObjects().begin(); i != m_board->GetSelectedObjects().end(); i++){
				(*i)->Move(m_so_move_board_dx, m_so_move_board_dy);
			}*/
			/*m_board->ChangedSomething();
			CalcSelectionThings();
			Refresh();*/
			SetMouseMode(MODE_SELECT);
		}
		break;
	case MODE_MOVE_JUMPER_WIRE_HEAD:
		{
			m_selected_jumper_wire = NULL;
			m_selected_jumper_wire_head = -1;
			SetMouseMode(MODE_SELECT);
			Refresh();
		}
		break;
#if 0
	case MODE_MOVE_JUMPER_WIRE_HEAD_AFTER_ADD:
		{
			m_selected_jumper_wire = NULL;
			m_selected_jumper_wire_head = -1;
			SetMouseMode(MODE_ADD_JUMPER_WIRE);
			Refresh();
		}
		break;
	case MODE_MOVE_JUMPER_WIRE_HEAD_AFTER_ADD_BOTTOM:
		{
			m_selected_jumper_wire = NULL;
			m_selected_jumper_wire_head = -1;
			SetMouseMode(MODE_ADD_JUMPER_WIRE_BOTTOM);
			Refresh();
		}
		break;
#endif
	case MODE_MOVE_COMPONENT_HEAD:
		{
			if(m_mch_component){
				DrawTempLine(m_mch_templine_sx1, m_mch_templine_sy1,
						m_mch_templine_sx2, m_mch_templine_sy2);

				//we should have a new place for the head now.
				BoardPoint pmoved(m_mch_move_pin_x, m_mch_move_pin_y);
				BoardPoint pother(m_mch_other_pin_x, m_mch_other_pin_y);

				//check the place and do nothing if it's not good
				if((pmoved.m_x != pother.m_x && pmoved.m_y != pother.m_y) || pmoved == pother){
					//SetMouseMode(MODE_SELECT);
					break;
				}

				//now, thrash the old component and make a new one

				//keep the parameters
				wxString refdes = m_mch_component->GetRefdes();
				wxString value = m_mch_component->GetValue();

				//keep the footprint type
				FootprintType t = FOOTPRINT_ACY;
				if(m_mch_component->GetFootprint()->m_name.substr(0,3) == wxT("ACY"))
						t = FOOTPRINT_ACY;
				else if(m_mch_component->GetFootprint()->m_name.substr(0,3) == wxT("ALF"))
						t = FOOTPRINT_ALF;
				else if(m_mch_component->GetFootprint()->m_name.substr(0,3) == wxT("ACC"))
						t = FOOTPRINT_ACC;
				
				//keep the selection value
				bool was_selected = m_mch_component->GetSelected();

				//delete it
				std::set<JukuObject*> todelete;
				todelete.insert(m_mch_component);
				m_board->Clear(todelete);

				//if selected_objects has it, remove from there too
				/*std::set<JukuObject*>::const_iterator i = find(m_board->GetSelectedObjects().begin(),
						m_board->GetSelectedObjects().end(), (JukuObject*)m_mch_component);
				if(i != m_board->GetSelectedObjects().end()) m_board->GetSelectedObjects().erase(i);*/
				
				m_mch_component = NULL;

				//create a new one

				Component *c = NULL;

				if(m_mch_moved_head == wxT("1"))
					c = MakeTwoWireComponent(t, pmoved, pother, refdes, value);
				else
					c = MakeTwoWireComponent(t, pother, pmoved, refdes, value);

				if(c==NULL){
					std::cout<<"failed to make component (this shouldn't happen)"<<std::endl;
				}
				else{
					std::cout<<"phew!"<<std::endl;
					if(was_selected){
						c->SetSelected(true);
						//m_board->GetSelectedObjects().push_back(c);
					}
					m_board->Add(c, NULL);
				}
				//SetMouseMode(MODE_SELECT);
			}
		}
		break;
	/*case MODE_ADD_JUMPER_WIRE:
		break;
	case MODE_ADD_JUMPER_WIRE_BOTTOM:
		break;*/
	case MODE_ADD_CUT:
		{
			//get the objects that would be selected by clicking at this point
			//and just ignore the event if there is a cut in this point
			//TODO: Something like m_board->GetObjectsAtPoint() would be
			//      better

			std::set<JukuObject*> objects = m_board->GetSelectedObjects(
					board_x, board_y, board_x, board_y, true);
			
			bool there_is_a_cut_in_here = false;
			for(std::set<JukuObject*>::iterator i = objects.begin();
					i != objects.end(); i++){
				if((*i)->GetType() == TYPE_CUT){
					there_is_a_cut_in_here = true;
					break;
				}
			}
			if(there_is_a_cut_in_here){
				//SetMouseMode(MODE_SELECT);
				break;
			}

			// create a new cut
			Cut *cut = new Cut();
			//add it to the board
			int board_x_holes = (board_x-50+50)/100;
			int board_y_holes = (board_y-50+50)/100;
			int board_x_centered = board_x_holes * 100;
			int board_y_centered = board_y_holes * 100;
			cut->SetBoardPos(BoardPoint(board_x_centered, board_y_centered));
			// select nothing
			if(m_board) m_board->SetSelected(false);
			//cut->SetSelected(true);
			//m_board->GetSelectedObjects().push_back(cut);
			// add it to the board
			m_board->Add(cut, NULL);
			//SetMouseMode(MODE_SELECT);
		}
		break;
	case MODE_ADD_NARROWCUT:
		{
			//get the objects that would be selected by clicking at this point
			//and just ignore the event if there is a cut in this point
			//TODO: Something like m_board->GetObjectsAtPoint() would be
			//      better

			std::set<JukuObject*> objects = m_board->GetSelectedObjects(
					board_x, board_y, board_x, board_y, true);
			
			bool there_is_an_ncut_in_here = false;
			for(std::set<JukuObject*>::iterator i = objects.begin();
					i != objects.end(); i++){
				if((*i)->GetType() == TYPE_NARROWCUT){
					there_is_an_ncut_in_here = true;
					break;
				}
			}
			if(there_is_an_ncut_in_here){
				//SetMouseMode(MODE_SELECT);
				break;
			}

			// create a new cut
			NarrowCut *ncut = new NarrowCut();
			//add it to the board
			int board_x_holes = (board_x-50+50-50)/100;
			int board_y_holes = (board_y-50+50)/100;
			int board_x_centered = board_x_holes * 100;
			int board_y_centered = board_y_holes * 100;
			ncut->SetBoardPos(BoardPoint(board_x_centered, board_y_centered));
			// select nothing
			if(m_board) m_board->SetSelected(false);
			//cut->SetSelected(true);
			//m_board->GetSelectedObjects().push_back(cut);
			// add it to the board
			m_board->Add(ncut, NULL);
			/*// refresh
			CalcSelectionThings();
			Refresh();*/
			//SetMouseMode(MODE_SELECT);
		}
		break;
	case MODE_ADD_RESISTOR:
	case MODE_ADD_DIODE:
	case MODE_ADD_CERAMIC:
		{
			//if it was not down, we shouldn't be here in a normal case, so ignore it,
			//as at least the values would probably be something random
			if(m_ac_left_down){
				DrawTempLine(m_ac_templine_sx1, m_ac_templine_sy1,
						m_ac_templine_sx2, m_ac_templine_sy2);

				/*std::cout<<"component would go to "
						<<m_ac_board_x1_centered<<","
						<<m_ac_board_y1_centered<<","
						<<m_ac_board_x2_centered<<","
						<<m_ac_board_y2_centered
						<<std::endl;*/

				BoardPoint p1(m_ac_board_x1_centered, m_ac_board_y1_centered);
				BoardPoint p2(m_ac_board_x2_centered, m_ac_board_y2_centered);

				//check the place and do nothing if it's not good
				if(p1 != p2){
					Component *c = NULL;
					if(m_mousemode == MODE_ADD_RESISTOR)
						c = MakeTwoWireComponent(FOOTPRINT_ACY, p1, p2, wxT("?"), wxT(""));
					else if(m_mousemode == MODE_ADD_DIODE)
						c = MakeTwoWireComponent(FOOTPRINT_ALF, p1, p2, wxT("?"), wxT(""));
					else if(m_mousemode == MODE_ADD_CERAMIC)
						c = MakeTwoWireComponent(FOOTPRINT_ACC, p1, p2, wxT("?"), wxT(""));

					if(c){
						m_board->Add(c, NULL);
					}
				}
				m_ac_left_down = false;
			}
			// If this is removed, the mode has to be 'reset' instead
			//SetMouseMode(MODE_SELECT);
		}
		break;
	case MODE_ADD_JUMPER_WIRE:
	case MODE_ADD_JUMPER_WIRE_BOTTOM:
		{
			//if it was not down, we shouldn't be here in a normal case, so ignore it,
			//as at least the values would probably be something random
			if(m_ac_left_down){
				DrawTempLine(m_ac_templine_sx1, m_ac_templine_sy1,
						m_ac_templine_sx2, m_ac_templine_sy2);

				BoardPoint p1(m_ac_board_x1_centered, m_ac_board_y1_centered);
				BoardPoint p2(m_ac_board_x2_centered, m_ac_board_y2_centered);

				//check the place and do nothing if it's not good
				if(p1 != p2){
					JumperWire *wire = new JumperWire();
					wire->SetHeadPoint(1, p1);
					wire->SetHeadPoint(2, p2);
					if(m_mousemode==MODE_ADD_JUMPER_WIRE_BOTTOM) wire->SetSide(BOTTOM);
					if(m_board) m_board->SetSelected(false);
					//ObjectSetSelected(wire, true);
					//wire->SetSelected(true);
					m_board->Add((JukuObject*)wire, NULL);
				}
				m_ac_left_down = false;
			}
			// If this is removed, the mode has to be 'reset' instead
			//SetMouseMode(MODE_SELECT);
		}
		break;
	case MODE_DELETE:
		{
			std::set<JukuObject*> objects = m_board->GetSelectedObjects(
					board_x, board_y,
					board_x, board_y, true);
			if(objects.size() == 1){
				m_board->Clear(objects);
			}
		}
		break;
	default:
		std::cerr<<"WARNING: Invalid mousemode\n"<<std::endl;
	}
}

void BoardScrolledWindow::OnMiddleDown(wxMouseEvent& event)
{
	//std::cout<<"BoardScrolledWindow::OnMiddleDown"<<std::endl;

	if(!m_board) return;
	
	int wx = event.GetX();
	int wy = event.GetY();
	//int sx = wx + m_last_px_scroll_x;
	//int sy = wy + m_last_px_scroll_y;
	//int board_x = sx / m_scale - m_board_padding_log_x;
	//int board_y = sy / m_scale - m_board_padding_log_y;
	m_middle_start_mouse_wx = wx;
	m_middle_start_mouse_wy = wy;
	CalcUnscrolledPosition(0, 0, &m_middle_start_scroll_x, &m_middle_start_scroll_y);
	m_middle_is_down = true;
	m_middle_drag_triggered = false;
}

void BoardScrolledWindow::OnMiddleUp(wxMouseEvent& event)
{
	//std::cout<<"BoardScrolledWindow::OnMiddleUp"<<std::endl;

	if(!m_board) return;
	
	/*int wx = event.GetX();
	int wy = event.GetY();
	int sx = wx + m_last_px_scroll_x;
	int sy = wy + m_last_px_scroll_y;
	int board_x = sx / m_scale - m_board_padding_log_x;
	int board_y = sy / m_scale - m_board_padding_log_y;*/
	
#if 0
	//if no drag has been done and a footprint is selected in the list box,
	//attempt to add a component
	Footprint *supplied_footprint = wxGetApp().m_frame->GetSelectedFootprint();
	if(!m_middle_drag_triggered && supplied_footprint != NULL){
		SetMouseMode(MODE_SELECT); //set mode to idle to avoid confusion
		//std::cout<<"creating a new component from the footprint"<<std::endl;
		// 1) create a new component with the footprint
		Component *component = new Component();
		//std::cout<<"made component "<<(unsigned int)component<<std::endl;
		component->SetRefdes(wxT("unknown"));
		//add it to the board
		int board_x_holes = (board_x-50+50)/100;
		int board_y_holes = (board_y-50+50)/100;
		int board_x_centered = board_x_holes * 100;
		int board_y_centered = board_y_holes * 100;
		component->SetBoardPos(BoardPoint(board_x_centered, board_y_centered));
		/*std::cout<<"supplied footprint is "<<supplied_footprint
				<<", making new"<<std::endl;*/
		Footprint *footprint = new Footprint(*supplied_footprint);
		//std::cout<<"made new footprint "<<footprint<<std::endl;
		component->SetFootprint(footprint);
		// 2) select only it
		//std::cout<<"selecting component"<<std::endl;
		if(m_board) m_board->SetSelected(false);
		component->SetSelected(true);
		m_board->GetSelectedObjects().push_back(component);
		// 3) add it to the board
		m_board->Add(component, NULL);
		// 4) refresh
		CalcSelectionThings();
		Refresh();
	}
#endif

	m_middle_is_down = false;
}

void BoardScrolledWindow::OnMotion(wxMouseEvent& event)
{

	if(!m_board) return;
	
	int wx = event.GetX();
	int wy = event.GetY();
	int sx = wx + m_last_px_scroll_x;
	int sy = wy + m_last_px_scroll_y;
	int board_x = sx / m_scale - m_board_padding_log_x;
	int board_y = sy / m_scale - m_board_padding_log_y;
	int hx = (board_x-50+50)/100;
	int hy = (board_y-50+50)/100;

	//
	{
	wxClientDC dc(this);
	DoPrepareDC(dc);
	
	dc.SetLogicalFunction(wxINVERT);

	wxPen pen(wxColour(0,0,0), 0);
	wxBrush brush(wxColour(0,0,0), wxTRANSPARENT);
	dc.SetPen(pen);
	dc.SetBrush(brush);

	int sx1, sy1, sw, sh;
	sw = m_scale * 100;
	sh = m_scale * 100;

	if(m_holerectangle_drawn){
		sx1 = m_scale * (m_last_mouse_hx * 100 + m_board_padding_log_x + 50 - 50);
		sy1 = m_scale * (m_last_mouse_hy * 100 + m_board_padding_log_y + 50 - 50);
		dc.DrawRectangle(sx1, sy1, sw, sh);
		m_holerectangle_drawn = false;
	}
	sx1 = m_scale * (hx * 100 + m_board_padding_log_x + 50 - 50);
	sy1 = m_scale * (hy * 100 + m_board_padding_log_y + 50 - 50);
	dc.DrawRectangle(sx1, sy1, sw, sh);
	m_holerectangle_drawn = true;
	}
	//

	if(m_middle_is_down){
		int mouse_dx = wx - m_middle_start_mouse_wx;
		int mouse_dy = wy - m_middle_start_mouse_wy;
		if(!m_middle_drag_triggered){
			int distance = sqrt(pow(mouse_dx, 2) + pow(mouse_dy, 2));
			if(distance > wxGetApp().m_settings.m_drag_hysteresis) m_middle_drag_triggered = true;
		}
		if(m_middle_drag_triggered){
			int new_scroll_x = m_middle_start_scroll_x - mouse_dx;
			int new_scroll_y = m_middle_start_scroll_y - mouse_dy;
			int units_x, units_y;
			GetScrollPixelsPerUnit(&units_x, &units_y);
			Scroll(new_scroll_x / units_x, new_scroll_y / units_y);
		}
	}

	switch(m_mousemode){
	case MODE_SELECT:
		break;
	case MODE_LEFT_SELECT:
		{
			int distance_from_start = sqrt(pow(m_left_down_sx - sx, 2)
					+ pow(m_left_down_sy - sy, 2));
			
			if(distance_from_start > wxGetApp().m_settings.m_drag_hysteresis){
				//mode to drag mode
				//std::cout<<"drag mode"<<std::endl;
				SetMouseMode(MODE_LEFT_SELECT_RECTANGLE);
				//draw selection rectangle
				DrawSelectRect(sx, sy);
			}
		}
		break;
	case MODE_LEFT_SELECT_RECTANGLE:
		{
			//remove old selection rectangle (by drawing again on top of it)
			DrawSelectRect(m_last_mouse_sx, m_last_mouse_sy);
			//draw new selection rectangle
			DrawSelectRect(sx, sy);
		}
		break;
	case MODE_DOWN_ON_SELECTION:
		{
			int distance_from_start = sqrt(pow(m_left_down_sx - sx, 2)
					+ pow(m_left_down_sy - sy, 2));
			
			if(distance_from_start > wxGetApp().m_settings.m_drag_hysteresis){
				bool something_done = false;
				//get the objects that would be selected by clicking at the down point
				std::set<JukuObject*> objects = m_board->GetSelectedObjects(
						m_left_down_board_x, m_left_down_board_y,
						m_left_down_board_x, m_left_down_board_y, true);
				if(!something_done){
					//search for a jumper wire with a head matching to the coordinates
					//of the click and which is selected already
					for(std::set<JukuObject*>::iterator i = objects.begin(); i != objects.end(); i++){
						JukuObject *object = (*i);
						if(object->GetSelected() == false) continue;
						//if the object is a jumper wire
						if(object->GetType() == TYPE_JUMPER_WIRE){
							//std::cout<<"it is jumper wire"<<std::endl;
							JumperWire *wire = (JumperWire*)object;
							//check if the wire has its head at the point
							int head = wire->GetHeadAtPoint(m_left_down_board_x-50,
															m_left_down_board_y-50);
							//std::cout<<"head="<<head<<std::endl;
							if(head != -1){
								//yes, begin a jumper wire head moving operation
								m_selected_jumper_wire_head = head;
								m_selected_jumper_wire = wire;
								m_sjwh_orig_point = wire->GetHeadPoint(head);
								SetMouseMode(MODE_MOVE_JUMPER_WIRE_HEAD);
								UpdateMoveJumperWireHead(board_x, board_y);
								something_done = true;
							}
						}
					}
				}
				if(!something_done){
					//go to move mode if some object that would be selected by clicking at
					//this point is already selected or if pressing the shift key
					bool start_move_selection = GetShiftState();
					if(!start_move_selection){
						for(std::set<JukuObject*>::iterator i = objects.begin(); i != objects.end(); i++){
							if((*i)->GetSelected()){
								start_move_selection = true;
								break;
							}
						}
					}
					if(start_move_selection){
						SetMouseMode(MODE_MOVE_SELECTION);
						UpdateMoveSelection(m_left_down_board_x, m_left_down_board_y);
						something_done = true;
					}
				}
			}
		}
		break;
	case MODE_MOVE_SELECTION:
		{
			UpdateMoveSelection(board_x, board_y);
		}
		break;
	case MODE_MOVE_JUMPER_WIRE_HEAD:
		{
			UpdateMoveJumperWireHead(board_x, board_y);
		}
		break;
	/*case MODE_MOVE_JUMPER_WIRE_HEAD_AFTER_ADD:
		{
			UpdateMoveJumperWireHead(board_x, board_y);
		}
		break;
	case MODE_MOVE_JUMPER_WIRE_HEAD_AFTER_ADD_BOTTOM:
		{
			UpdateMoveJumperWireHead(board_x, board_y);
		}
		break;*/
	case MODE_MOVE_COMPONENT_HEAD:
		{
			if(m_mch_component){
				DrawTempLine(m_mch_templine_sx1, m_mch_templine_sy1,
						m_mch_templine_sx2, m_mch_templine_sy2);
						
				int board_x2_holes = (board_x-50+50)/100;
				int board_y2_holes = (board_y-50+50)/100;
				m_mch_move_pin_x = board_x2_holes * 100;
				m_mch_move_pin_y = board_y2_holes * 100;
				m_mch_templine_sx2 = m_scale * (m_mch_move_pin_x + m_board_first_hole_log_x);
				m_mch_templine_sy2 = m_scale * (m_mch_move_pin_y + m_board_first_hole_log_y);

				DrawTempLine(m_mch_templine_sx1, m_mch_templine_sy1,
						m_mch_templine_sx2, m_mch_templine_sy2);
			}
		}
		break;
	/*case MODE_ADD_JUMPER_WIRE:
		break;
	case MODE_ADD_JUMPER_WIRE_BOTTOM:
		break;*/
	case MODE_ADD_CUT:
		break;
	case MODE_ADD_NARROWCUT:
		break;
	case MODE_ADD_JUMPER_WIRE:
	case MODE_ADD_JUMPER_WIRE_BOTTOM:
	case MODE_ADD_RESISTOR:
	case MODE_ADD_DIODE:
	case MODE_ADD_CERAMIC:
		{
			if(m_ac_left_down){
				DrawTempLine(m_ac_templine_sx1, m_ac_templine_sy1,
						m_ac_templine_sx2, m_ac_templine_sy2);
				//int board_x1_holes = (m_left_down_board_x-50+50)/100;
				//int board_y1_holes = (m_left_down_board_y-50+50)/100;
				//m_ac_board_x1_centered = board_x1_holes * 100;
				//m_ac_board_y1_centered = board_y1_holes * 100;
				int board_x2_holes = (board_x-50+50)/100;
				int board_y2_holes = (board_y-50+50)/100;
				m_ac_board_x2_centered = board_x2_holes * 100;
				m_ac_board_y2_centered = board_y2_holes * 100;
				//m_ac_templine_sx1 = m_scale * (m_ac_board_x1_centered + m_board_first_hole_log_x);
				//m_ac_templine_sy1 = m_scale * (m_ac_board_y1_centered + m_board_first_hole_log_y);
				m_ac_templine_sx2 = m_scale * (m_ac_board_x2_centered + m_board_first_hole_log_x);
				m_ac_templine_sy2 = m_scale * (m_ac_board_y2_centered + m_board_first_hole_log_y);
				DrawTempLine(m_ac_templine_sx1, m_ac_templine_sy1,
						m_ac_templine_sx2, m_ac_templine_sy2);
			}
		}
		break;
	case MODE_DELETE:
		break;
	default:
		std::cerr<<"WARNING: Invalid mousemode\n"<<std::endl;
	}
	m_last_mouse_sx = sx;
	m_last_mouse_sy = sy;
	m_last_mouse_hx = hx;
	m_last_mouse_hy = hy;
}

//#define SCALE_ZOOM_STEP_FACTOR 1.2

void BoardScrolledWindow::OnMouseWheel(wxMouseEvent& event)
{
	//std::cout<<"BoardScrolledWindow::OnMouseWheel"<<std::endl;

	event.Skip();
	

	if(!m_board) return;
	
	int rotation = event.GetWheelRotation();
	int delta = event.GetWheelDelta();
	m_zoom_rotation += rotation;
	while(m_zoom_rotation >= delta){
		m_zoom_rotation -= delta;
	}
	while(m_zoom_rotation <= -delta){
		m_zoom_rotation += delta;
	}
	//RefreshRect(wxRect(0, 0, px_size_x, px_size_y));
}

void BoardScrolledWindow::OnChar(wxKeyEvent &event)
{
	//std::cout<<"BoardScrolledWindow::OnChar()"<<std::endl;
	//int keycode = event.GetKeyCode();
	//std::cout<<"keycode='"<<(char)keycode<<"'="<<keycode<<std::endl;
	
	//did nothing, pass on
	event.Skip();
}

void BoardScrolledWindow::OnKeyDown(wxKeyEvent &event)
{
	//std::cout<<"BoardScrolledWindow::OnKeyDown()"<<std::endl;
	wxUint32 keycode = event.m_keyCode;
	//std::cout<<"keycode="<<keycode<<std::endl;

	switch(keycode)
	{
	case WXK_CONTROL:
		m_keystate_control = true;
		event.Skip();
		break;
	case WXK_SHIFT:
		m_keystate_shift = true;
		event.Skip();
		break;
	default:
		event.Skip();
	}
}

void BoardScrolledWindow::OnKeyUp(wxKeyEvent &event)
{
	//std::cout<<"BoardScrolledWindow::OnKeyUp()"<<std::endl;
	wxUint32 keycode = event.m_keyCode;
	//std::cout<<"keycode="<<keycode<<std::endl;

	switch(keycode)
	{
	case WXK_CONTROL:
		m_keystate_control = false;
		event.Skip();
		break;
	case WXK_SHIFT:
		m_keystate_shift = false;
		event.Skip();
		break;
	/*case WXK_BACK:
		wxGetApp().m_frame->FocusFootprintSearch();
		break;
	case WXK_RETURN:
		wxGetApp().m_frame->FocusFootprintList();
		break;*/
	default:
		event.Skip();
	}
}

void BoardScrolledWindow::SetModeStaticText(wxStaticText *t)
{
	m_modestatictext = t;
	UpdateModeStaticText();
}

void BoardScrolledWindow::OnBoardSizeOrScale()
{
	CalcBoardOffsets();
	UpdateVirtualSize();
	CalcSelectionThings();
	Refresh();
}

void BoardScrolledWindow::SetBoard(Board *board)
{
	//std::cerr<<"BoardScrolledWindow::SetBoard("<<(unsigned int)board<<")"<<std::endl;
	m_board = board;
	if(m_board){
		m_scale = m_board->GetSaveScale();
		m_board->AddChangeHandler(this);
	}
	if(m_board) m_board->SetSelected(false);
	OnBoardSizeOrScale();
}

void BoardScrolledWindow::SetScale(double scale)
{
	if(m_board) m_board->SetSaveScale(scale);
	m_scale = scale;
	OnBoardSizeOrScale();
}

double BoardScrolledWindow::GetScale()
{
	return m_scale;
}

void BoardScrolledWindow::SupplyFootprint(Footprint *supplied_footprint)
{
	//std::cout<<"BoardScrolledWindow::SupplyFootprint()"<<std::endl;

	if(m_board == NULL) return;

	if(m_board->GetSelectedObjects().empty()){
		//std::cout<<"creating a new component from the footprint"<<std::endl;
		// create a new component with the footprint
		Component *component = new Component();
		//std::cout<<"made component "<<(unsigned int)component<<std::endl;
		component->SetRefdes(wxT("unknown"));
		/*std::cout<<"supplied footprint is "<<supplied_footprint
				<<", making new"<<std::endl;*/
		Footprint *footprint = new Footprint(*supplied_footprint);
		//std::cout<<"made new footprint "<<footprint<<std::endl;
		component->SetFootprint(footprint);

		m_board->ThrowComponent(component);
		//ThrowComponent(component);
	}
	else{
		for(std::set<JukuObject*>::const_iterator i = m_board->GetSelectedObjects().begin();
				i != m_board->GetSelectedObjects().end(); i++){
			if((*i)->GetType() != TYPE_COMPONENT) continue;
			Component *c = (Component*)(*i);
			Footprint *footprint = new Footprint(*supplied_footprint);
			c->SetFootprint(footprint);
			BoardRect r = c->GetPinExtents();
			//stick to holes
			int mx = r.m_x % 100;
			int my = r.m_y % 100;
			if(mx >= 50) mx = mx - 100;
			if(my >= 50) my = my - 100;
			c->Move(mx, my);
		}
	}
}

BoardPoint BoardScrolledWindow::GetVisibleCenterPos()
{
	int view_px_w = 0;
	int view_px_h = 0;
	GetClientSize(&view_px_w, &view_px_h);
	int sx, sy;
	if(m_scale * (m_board->GetW() * 100 + m_board_padding_log_x*2) > view_px_w)
		sx = m_last_px_scroll_x + view_px_w / 2;
	else sx = m_last_px_scroll_x
				+ m_scale * (m_board->GetW() * 100 + m_board_padding_log_x*2) / 2;
	if(m_scale * (m_board->GetH() * 100 + m_board_padding_log_x*2) > view_px_h)
		sy = m_last_px_scroll_y + view_px_h / 2;
	else sy = m_last_px_scroll_y
				+ m_scale * (m_board->GetH() * 100 + m_board_padding_log_x*2) / 2;
	int board_x = sx / m_scale - m_board_padding_log_x;
	int board_y = sy / m_scale - m_board_padding_log_y;
	int board_x_holes = (board_x-50+50)/100;
	int board_y_holes = (board_y-50+50)/100;
	int board_x_centered = board_x_holes * 100;
	int board_y_centered = board_y_holes * 100;
	return BoardPoint(board_x_centered, board_y_centered);
}



